/*
 * SpringAwareCacheFactory.java
 *
 * Copyright 2001-2007 by Oracle. All rights reserved.
 *
 * Oracle is a registered trademarks of Oracle Corporation and/or its affiliates.
 *
 * This software is the confidential and proprietary information of
 * Oracle Corporation. You shall not disclose such confidential and
 * proprietary information and shall use it only in accordance with the
 * terms of the license agreement you entered into with Oracle.
 *
 * This notice may not be removed or altered.
 */
package com.tangosol.coherence.spring;

import java.util.Iterator;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

import com.tangosol.net.BackingMapManagerContext;
import com.tangosol.net.DefaultConfigurableCacheFactory;
import com.tangosol.net.DefaultConfigurableCacheFactory.CacheInfo;
import com.tangosol.run.xml.SimpleElement;
import com.tangosol.run.xml.XmlElement;
import com.tangosol.run.xml.XmlHelper;
import com.tangosol.util.ClassHelper;

/**
 * SpringAwareCacheFactory provides a facility to access caches declared in a "cache-config.dtd" compliant configuration
 * file, similar to its super class {@link DefaultConfigurableCacheFactory}. In addition, this factory provides the
 * ability to reference beans in a Spring application context via the use of a class-scheme element.
 * 
 * <p>
 * This factory can be configured to start its own Spring application context from which to retrieve these beans. This
 * can be useful for stand-alone JVMs such as cache servers. It can also be configured at runtime with a pre-configured
 * Spring bean factory. This can be useful for Coherence applications running in an environment that is itself
 * responsible for starting the Spring bean factory, such as a web container.
 * 
 * @see #instantiateAny(CacheInfo, XmlElement, BackingMapManagerContext, ClassLoader)
 * 
 * @author pperalta Jun 14, 2007
 */
public class SpringAwareCacheFactory extends DefaultConfigurableCacheFactory implements BeanFactoryAware {
    // ----- constructors -------------------------------------------------

    /**
     * Construct a default DefaultConfigurableCacheFactory using the default configuration file name.
     */
    public SpringAwareCacheFactory() {
        super();
    }


    /**
     * Construct a SpringAwareCacheFactory using the specified path to a "cache-config.dtd" compliant configuration file.
     * 
     * @param sPath the configuration resource name or file path
     */
    public SpringAwareCacheFactory(final String sPath) {
        super(sPath);        
    }

    // ----- extended methods -----------------------------------------------

    /**
     * Create an Object using the "class-scheme" element. <p/> In addition to the functionality provided by the super
     * class, this will retreive an object from the configured Spring BeanFactory for class names that use the following
     * format:
     * 
     * <pre>
     * &lt;class-name&gt;spring-bean:sampleCacheStore&lt;/class-name&gt;
     * </pre>
     * 
     * Parameters may be passed to these beans via setter injection as well:
     * 
     * <pre>
     *   &lt;init-params&gt;
     *     &lt;init-param&gt;
     *       &lt;param-name&gt;setEntityName&lt;/param-name&gt;
     *       &lt;param-value&gt;{cache-name}&lt;/param-value&gt;
     *     &lt;/init-param&gt;
     *   &lt;/init-params&gt;
     * </pre>
     * 
     * Note that Coherence will manage the lifecycle of the instantiated Spring bean, therefore any beans that are
     * retreived using this method should be scoped as a prototype in the Spring configuration file, for example:
     * 
     * <pre>
     *   &lt;bean id=&quot;sampleCacheStore&quot;
     *         class=&quot;com.company.SampleCacheStore&quot;
     *         scope=&quot;prototype&quot;/&gt;
     * </pre>
     * 
     * @param info the cache info
     * @param xmlClass "class-scheme" element.
     * @param context BackingMapManagerContext to be used
     * @param loader the ClassLoader to instantiate necessary classes
     * 
     * @return a newly instantiated Object
     * 
     * @see DefaultConfigurableCacheFactory#instantiateAny(CacheInfo, XmlElement, BackingMapManagerContext, ClassLoader)
     */
    @Override
    public Object instantiateAny(final CacheInfo info, final XmlElement xmlClass,
            final BackingMapManagerContext context, final ClassLoader loader) {
        if (translateSchemeType(xmlClass.getName()) != SCHEME_CLASS) {
            throw new IllegalArgumentException("Invalid class definition: " + xmlClass);
        }

        String sClass = xmlClass.getSafeElement("class-name").getString();

        if (sClass.startsWith(SPRING_BEAN_PREFIX)) {
            String sBeanName = sClass.substring(SPRING_BEAN_PREFIX.length());

            azzert(sBeanName != null && sBeanName.length() > 0, "Bean name required");

            XmlElement xmlParams = xmlClass.getElement("init-params");
            XmlElement xmlConfig = null;
            if (xmlParams != null) {
                xmlConfig = new SimpleElement("config");
                XmlHelper.transformInitParams(xmlConfig, xmlParams);
            }

            Object oBean = getBeanFactory().getBean(sBeanName);

            if (xmlConfig != null) {
                for (Iterator iter = xmlConfig.getElementList().iterator(); iter.hasNext();) {
                    XmlElement xmlElement = (XmlElement) iter.next();

                    String sMethod = xmlElement.getName();
                    String sParam = xmlElement.getString();
                    try {
                        ClassHelper.invoke(oBean, sMethod, new Object[] { sParam });
                    } catch (Exception e) {
                        ensureRuntimeException(e, "Could not invoke " + sMethod + "(" + sParam + ") on bean " + oBean);
                    }
                }
            }
            return oBean;
        } else {
            return super.instantiateAny(info, xmlClass, context, loader);
        }
    }

    /**
     * Get the Spring BeanFactory used by this CacheFactory
     * 
     * @return the Spring {@link BeanFactory} used by this CacheFactory
     */
    public BeanFactory getBeanFactory() {
        azzert(this.m_beanFactory != null, "Spring BeanFactory == null");
        return this.m_beanFactory;
    }

    /**
     * Set the Spring BeanFactory used by this CacheFactory
     * 
     * @param beanFactory the Spring {@link BeanFactory} used by this CacheFactory
     */
    public void setBeanFactory(final BeanFactory beanFactory) {
        if (this.m_beanFactory == null)
            this.m_beanFactory = beanFactory;
    }

    // ----- data fields ----------------------------------------------------

    /**
     * Spring BeanFactory used by this CacheFactory
     */
    private BeanFactory m_beanFactory;

    /**
     * Prefix used in cache configuration "class-name" element to indicate this bean is in Spring
     */
    private static final String SPRING_BEAN_PREFIX = "spring-bean:";
}
